/*******************************************************************************
 * Copyright (c) 2011 GigaSpaces Technologies Ltd. All rights reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package org.cloudifysource.rest.controllers;

import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;

import org.cloudifysource.dsl.utils.IPUtils;
import org.cloudifysource.rest.command.CommandManager;
import org.cloudifysource.rest.out.OutputDispatcher;
import org.cloudifysource.rest.util.NotFoundHttpException;
import org.openspaces.admin.Admin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

/**
 * Spring MVC controller for the RESTful Admin API via a reflection-based
 * implementation of dispatcher pattern
 * 
 * - Accepts a generic uri path which denotes a specific Admin request
 * 
 * - Parses and walks through the uri by activating getter methods to "dig into"
 * the admin object hierarchy
 * 
 * - Results marshaled as a generic document serialized to a JSON object
 * 
 * 
 * Usage examples: http://localhost:8099/admin/ElasticServiceManagers/Managers
 * http://localhost:8099/admin/GridServiceManagers/size
 * http://localhost:8099/admin/Spaces
 * http://localhost:8099/admin/VirtualMachines/VirtualMachines
 * http://localhost:8099/admin/VirtualMachines/VirtualMachines/3
 * http://localhost
 * :8099/admin/VirtualMachines/VirtualMachines/3/Statistics/Machine
 * /GridServiceAgents DETAILS:
 * http://localhost:8099/admin/GridServiceManagers/Uids
 * /49a6e2ef-5fd3-471a-94ff-c961a52ffd0f STATIST:
 * http://localhost:8099/admin/GridServiceManagers
 * /Uids/49a6e2ef-5fd3-471a-94ff-c961a52ffd0f
 * 
 * Note that the wiring and marshaling services are provided by Spring framework
 * 
 * Note 2: It is highly recommended that results will be viewed on FF with
 * JsonView plugin
 * 
 * @author giladh, adaml
 */

@Controller
@RequestMapping(value = "/admin/*")
public class AdminAPIController {

	@Autowired(required = true)
	private Admin admin;

	private static final Logger logger = Logger
			.getLogger(AdminAPIController.class.getName());

	/**
	 * redirects to index view.
	 * 
	 * @return a ModelAndView object with viewName = index
	 */
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public ModelAndView redirectToIndex() {
		return new ModelAndView("index");
	}

	/**
	 * REST GET requests handler wrapper.
	 * 
	 * @param httpServletRequest
	 *            The request
	 * @return The response as a map
	 * @throws Exception
	 *             Indicates the request failed
	 */
	@PreAuthorize("isFullyAuthenticated() and hasAnyRole('ROLE_CLOUDADMINS')")
	@RequestMapping(value = "/**", method = RequestMethod.GET)
	public @ResponseBody
	Map<String, Object> get(final HttpServletRequest httpServletRequest)
			throws Exception {
		return getImplementation(httpServletRequest);
	}

	/**
	 * REST GET requests handler implementation Parses uri path activates
	 * appropriate getters serialize results into a document object and pass for
	 * JSON marshaling
	 * 
	 * uri type processing ============ ============== http:/../getArr/ind/...
	 * => (intermed.) resolve to arr[ind] and continue processing
	 * http:/../getMap/key/... => (intermed.) resolve to map.get(key) and
	 * continue processing http:/../getList/ind/... => (intermed.) resolve to
	 * list(ind) and continue processing http:/../getObj => (final) return obj
	 * fields (by public getters) http:/../getArr => (final) return arr.length
	 * http:/../getList => (final) return list.size() http:/../getMap => (final)
	 * return comma-separated list of map keys
	 * 
	 * 
	 */
	private Map<String, Object> getImplementation(
			final HttpServletRequest httpServletRequest) throws Exception {
		// admin acts as root
		final CommandManager manager = new CommandManager(httpServletRequest,
				getAdmin());
		manager.runCommands();
		final String hostAddress = getRemoteHostAddress(httpServletRequest);
		final String hostContext = httpServletRequest.getContextPath();
		return OutputDispatcher.outputResultObjectToMap(manager, hostAddress,
				hostContext);
	}

	private String getRemoteHostAddress(
			final HttpServletRequest httpServletRequest) {
		final String host = httpServletRequest.getServerName();
		final int port = httpServletRequest.getServerPort();
		String url = IPUtils.getRestProtocol() + "://" + IPUtils.getSafeIpAddress(host) 
				+ ":" + port;
		return url;
	}

	public Admin getAdmin() {
		return admin;
	}

	@ExceptionHandler(NotFoundHttpException.class)
	@ResponseStatus(value = HttpStatus.NOT_FOUND)
	public void resolveNotFound(final Writer writer, final Exception e,
			final HttpServletRequest request) throws IOException {
		final String requestURL = request.getRequestURL().toString();
		logger.log(Level.INFO, "Cannot find URL: " + requestURL, e);
		writer.write("{\"status\":\"error\", \"error\":\""
				+ "Cannot find URL: " + requestURL + "cause: " + e.getMessage()
				+ "\"}");
	}

	@ExceptionHandler(Exception.class)
	@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
	public void resolveInternalServerError(final Writer writer,
			final Exception e) throws IOException {
		logger.log(Level.WARNING, "caught exception", e);
		writer.write("{\"status\":\"error\", \"error\":\"" + e.getMessage()
				+ "\"}");
	}
}
